package com.gylang.crypto.adapter;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.crypto.CryptoException;
import com.alibaba.fastjson.JSON;
import com.gylang.common.dto.CommonResult;
import com.gylang.common.enums.BaseResultCode;
import com.gylang.common.exception.BizException;
import com.gylang.crypto.annotation.CryptoResponse;
import com.gylang.crypto.crypto.CryptoContext;
import com.gylang.crypto.dto.BaseCryptoDTO;
import com.gylang.crypto.resolver.EncryptKeyResolver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.ArrayList;
import java.util.List;


/**
 * 响应后置处理器 对响应结果进行额外加工, 实现统一加密
 *
 * @author gylang
 * data 2020/10/28
 * @version v0.0.1
 */
@Slf4j
@ControllerAdvice
public class EncryptBodyAdviceAdapter implements InitializingBean, ResponseBodyAdvice<Object> {
    @Autowired
    private CryptoContext cryptoContext;
    @Autowired(required = false)
    private List<EncryptKeyResolver> encryptKeyResolverList;

    /**
     * 判断是否支持当前响应是否需要同一加密
     *
     * @param returnType 业务方法返回类型
     * @param converterType http数据转化器
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.info("CryptRequestBodyAdviceAdapter#supports");
        return null != returnType.getMethodAnnotation(CryptoResponse.class);
    }

    /**
     * 返回前对数据进行加工
     * @param body 业务执行结果对象
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 响应
        CryptoResponse cryptoResponse = returnType.getMethodAnnotation(CryptoResponse.class);
        assert cryptoResponse != null;

        // 加解密仅支持字符串 和 对象
        Object data;
        String code = null;
        String msg = null;
        // 判断是否为 通用封装响应
        if (body instanceof CommonResult) {
            CommonResult<?> result = (CommonResult<?>) body;
            // 响应结果不正确
            code = result.getCode();
            msg = result.getMsg();
            data = result.getData();
        } else {
            throw new BizException(BaseResultCode.SYSTEM_PROCESS_ERROR.getCode(), "不支持公共包装类加解密");
        }

        // 判断是否正常响应 如果非正常响应 直接将结果集返回
        if (!BaseResultCode.OK.getCode().equals(code)) {
            return body;
        }
        // 不支持基本数据类型
        if (ClassUtil.isBasicType(data.getClass())) {
            throw new CryptoException("不支持当前类型的加密");
        }
        //
        String resultStr = null;
        if (ClassUtil.isSimpleValueType(data.getClass())) {
            resultStr = Convert.convert(String.class, data);
        } else {
            resultStr = JSON.toJSONString(data);
        }
        // 获取密钥
        String encryptKey = cryptoResponse.encryptKey();
        String signKey = cryptoResponse.signKey();
        for (EncryptKeyResolver encryptKeyResolver : encryptKeyResolverList) {
            if (encryptKeyResolver.support(response, cryptoResponse, request, response)) {
                signKey = encryptKeyResolver.signKey(response, cryptoResponse, request, response);
                encryptKey = encryptKeyResolver.encryptKey(response, cryptoResponse, request, response);
            }
        }
        // 加签验签
        String encryptStr = cryptoContext.encrypt(cryptoResponse.type(), encryptKey, resultStr);
        String signStr = cryptoContext.sign(cryptoResponse.type(), signKey, resultStr);
        // 响应
        BaseCryptoDTO cryptResult = new BaseCryptoDTO();
        cryptResult.setCode(code);
        cryptResult.setMsg(msg);
        cryptResult.setData(encryptStr);
        cryptResult.setSign(signStr);
        return cryptResult;
    }


    @Override
    public void afterPropertiesSet() {
        if (null == encryptKeyResolverList) {
            encryptKeyResolverList = new ArrayList<>();
        }
    }
}
